using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Bzstd.Utility.Assertion;

namespace Bzstd.Utility.Date;

/// <summary>
/// 二十四节气
/// </summary>
[SuppressMessage("ReSharper", "IdentifierTypo")]
public readonly record struct SolarTerm
{
	/// <summary>
	/// 名称
	/// </summary>
	private string Name { get; }

	/// <summary>
	/// +1偏移
	/// </summary>
	private int[] IncreaseOffset { get; }

	/// <summary>
	/// -1偏移
	/// </summary>
	private int[] DecreaseOffset { get; }

	/// <summary>
	/// 20世纪的节气C值
	/// </summary>
	private double Century20 { get; }

	/// <summary>
	/// 21世纪的节气C值
	/// </summary>
	private double Century21 { get; }

	private SolarTerm(string name, int[] increaseOffset, int[] decreaseOffset, double century20, double century21)
	{
		Name = name;
		IncreaseOffset = increaseOffset;
		DecreaseOffset = decreaseOffset;
		Century20 = century20;
		Century21 = century21;
	}

	public override string ToString()
	{
		return Name;
	}

	#region 二十四节气

	/// <summary>
	/// 小寒
	/// </summary>
	/// <returns></returns>
	public static SolarTerm XIAOHAN { get; } = new("小寒", [1982], [2019], 6.11, 5.4055);

	/// <summary>
	/// 大寒
	/// </summary>
	/// <returns></returns>
	public static SolarTerm DAHAN { get; } = new("大寒", [2082], [], 20.84, 20.12);

	/// <summary>
	/// 立春
	/// </summary>
	public static SolarTerm LICHUN { get; } = new("立春", [], [], 4.6295, 3.87);

	/// <summary>
	/// 雨水
	/// </summary>
	/// <returns></returns>
	public static SolarTerm YUSHUI { get; } = new("雨水", [], [2026], 19.4599, 18.73);

	/// <summary>
	/// 惊蛰
	/// </summary>
	/// <returns></returns>
	public static SolarTerm JINGZHE { get; } = new("惊蛰", [], [], 6.3826, 20.646);

	/// <summary>
	/// 春分
	/// </summary>
	/// <returns></returns>
	public static SolarTerm CHUNFEN { get; } = new("春分", [2084], [], 21.4155, 20.646);

	/// <summary>
	/// 清明
	/// </summary>
	/// <returns></returns>
	public static SolarTerm QINGMING { get; } = new("清明", [], [], 5.59, 4.81);

	/// <summary>
	/// 谷雨
	/// </summary>
	/// <returns></returns>
	public static SolarTerm GUYU { get; } = new("谷雨", [], [], 20.888, 20.1);

	/// <summary>
	/// 立夏
	/// </summary>
	/// <returns></returns>
	public static SolarTerm LIXIA { get; } = new("立夏", [], [], 6.318, 5.52);

	/// <summary>
	/// 小满
	/// </summary>
	/// <returns></returns>
	public static SolarTerm XIAOMAN { get; } = new("小满", [2008], [], 21.86, 21.04);

	/// <summary>
	/// 芒种
	/// </summary>
	/// <returns></returns>
	public static SolarTerm MANGZHONG { get; } = new("芒种", [1902], [], 6.5, 5.678);

	/// <summary>
	/// 夏至
	/// </summary>
	/// <returns></returns>
	public static SolarTerm XIAZHI { get; } = new("夏至", [1928], [], 22.2, 21.37);

	/// <summary>
	/// 小暑
	/// </summary>
	/// <returns></returns>
	public static SolarTerm XIAOSHU { get; } = new("小暑", [1925, 2016], [], 7.928, 7.108);

	/// <summary>
	/// 大暑
	/// </summary>
	/// <returns></returns>
	public static SolarTerm DASHU { get; } = new("大暑", [1922], [], 23.65, 22.83);

	/// <summary>
	/// 立秋
	/// </summary>
	/// <returns></returns>
	public static SolarTerm LIQIU { get; } = new("立秋", [2002], [], 8.35, 7.5);

	/// <summary>
	/// 处暑
	/// </summary>
	/// <returns></returns>
	public static SolarTerm CHUSHU { get; } = new("处暑", [], [], 23.95, 23.13);

	/// <summary>
	/// 白露
	/// </summary>
	/// <returns></returns>
	public static SolarTerm BAILU { get; } = new("白露", [1927], [], 8.44, 7.646);

	/// <summary>
	/// 秋分
	/// </summary>
	/// <returns></returns>
	public static SolarTerm QIUFEN { get; } = new("秋分", [1942], [], 23.822, 23.042);

	/// <summary>
	/// 寒露
	/// </summary>
	/// <returns></returns>
	public static SolarTerm HANLU { get; } = new("寒露", [], [], 9.098, 8.318);

	/// <summary>
	/// 霜降
	/// </summary>
	/// <returns></returns>
	public static SolarTerm SHUANGJIANG { get; } = new("霜降", [2089], [], 24.218, 23.438);

	/// <summary>
	/// 立冬
	/// </summary>
	/// <returns></returns>
	public static SolarTerm LIDONG { get; } = new("立冬", [2089], [], 8.218, 7.438);

	/// <summary>
	/// 小雪
	/// </summary>
	/// <returns></returns>
	public static SolarTerm XIAOXUE { get; } = new("小雪", [1978], [], 23.08, 22.36);

	/// <summary>
	/// 大雪
	/// </summary>
	/// <returns></returns>
	public static SolarTerm DAXUE { get; } = new("大雪", [1954], [], 7.9, 7.18);

	/// <summary>
	/// 冬至
	/// </summary>
	/// <returns></returns>
	public static SolarTerm DONGZHI { get; } = new("冬至", [], [1918, 2021], 22.6, 21.94);

	#endregion

	/// <summary>
	/// 返回节气是相应月份的第几天
	/// </summary>
	/// <param name="year">年份</param>
	/// <param name="solarTerm">节气的名称</param>
	/// <returns></returns>
	private static int GetSolarTermNum(int year, SolarTerm solarTerm)
	{
		// 节气的世纪值，每个节气的每个世纪值都不同
		double centuryValue = year switch
		{
			>= 1901 and <= 2000 => solarTerm.Century20,
			>= 2001 and <= 2100 => solarTerm.Century21,
			_ => Assert.Fail<double>($"不支持此年份: {year}, 目前只支持1901年到2100年的时间范围")
		};

		// 计算num=[Y*D+C]-L这是传说中的寿星通用公式 公式解读: 年数的后2位乘0.2422加C (即: centuryValue) 取整数后, 减闰年数
		// 步骤1:取年分的后两位数
		int y = year % 100;
		// 闰年
		if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
		{
			if (new[] {XIAOHAN, DAHAN, LICHUN, YUSHUI}.Contains(solarTerm))
			{
				// 注意：凡闰年3月1日前闰年数要减一，即：L=[(Y-1)/4],因为小寒、大寒、立春、雨水这两个节气都小于3月1日,所以 --y
				// 步骤2
				--y;
			}
		}

		// 步骤3，使用公式[Y*D+C]-L计算
		const double D = 0.2422;
		int dateNum = (int) (y * D + centuryValue - y / 4);
		// 步骤4，特例,特殊的年分的节气偏移量,由于公式并不完善，所以算出的个别节气的第几天数并不准确，在此返回其偏移量
		if (solarTerm.DecreaseOffset.Contains(year))
		{
			--dateNum;
		}

		if (solarTerm.IncreaseOffset.Contains(year))
		{
			++dateNum;
		}

		return dateNum;
	}

	/// <summary>
	/// 获取二十四节气
	/// </summary>
	/// <param name="date"></param>
	/// <returns></returns>
	public static SolarTerm GetSolarTerm(DateTime date)
	{
		Assert.IsTrue(date > DateTime.MinValue, "date不能为空");
		var propertyInfos = typeof(SolarTerm).GetProperties(BindingFlags.Public | BindingFlags.Static);
		for (var i = 0; i < propertyInfos.Length; i++)
		{
			var solarTerm = (SolarTerm) propertyInfos[i].GetValue(null)!;
			if (solarTerm.Name != null && i / 2 == date.Month - 1 && GetSolarTermNum(date.Year, solarTerm) == date.Day)
			{
				return solarTerm;
			}
		}

		return default;
	}
}
